#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glut.h>
#include <GL/glui.h>

#include <math.h>
#include <time.h>
#include <stdio.h>
#include <stdlib.h>

#define OPENGL

#include <mcMath.h>
#include <mcFrustum.h>

mc::Frustum  frustum;


#define FALSE 0
#define TRUE  1

//constantes para a GLUT
int     janela;
int     largura;
int     altura;
int     iniX, iniY;
int     gira = FALSE;
int     translada = FALSE;
float   velocidadeFrente = 0;
float   velocidadeLado = 0;
float   dt = 0;
clock_t tempoant = 0;

/////////////////////////////////////////////////////////////////////////////////////
// Calcula a quantidade de quadros pos segundo dadas as variacoes de tempo. Atualiza
// a cada 500ms.
/////////////////////////////////////////////////////////////////////////////////////
float CalculaQuadrosSegundo(unsigned int dt) {
    static float ultimoqps = 0.0f;	// Guarda o ultimo qps calculado
    static unsigned int contaquadro  = 0;	// Conta o numero de quadros a cada chamada
    static unsigned int tempopassado = 0;	// Armazena o tempo do ultimo quadro

    // Incrementa o contador
    contaquadro++;
    tempopassado += dt;

    // Testa se o tempo passado soma 1 segundo
    if(tempopassado > 1000) {
        // Calcula quadros por segundo
	ultimoqps = ((float) contaquadro / (float) tempopassado) * 1000.f;

	// Zera contador de tempo
	tempopassado = 0;

        // Zera contador de quadros
        contaquadro = 0;
    }

    // Retorna quadros por segundo
    return ultimoqps;
}



void ConfiguraOpenGL(){
    glClearColor(0.6f,0.6f,0.6f,1.0f); //RGBA
    glShadeModel(GL_SMOOTH);

    //teste de buffer
    glClearDepth(1.0f);
    glEnable(GL_DEPTH_TEST);
    glDepthFunc(GL_LEQUAL);

    glEnable(GL_CULL_FACE);
    glEnable(GL_TEXTURE_2D);
    glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);

    //Definições da iluminação
    glColor3f(1.0f, 0.4f, 0.4f);
    GLfloat posicaoLuz[4] = { 25, 25, 50, 1.0};
    GLfloat luzDifusa[4] = {0.5, 0.5, 0.3, 1.0};   // "cor"
    //GLfloat luzEspecular[4] = {0.5, 0.4, 0.3,1.0};// "brilho"
    GLfloat especularidade[4] = {0.5, 0.555802, 0.366065,1.0};
    GLint especMaterial = 80;

    glLightfv(GL_LIGHT0, GL_POSITION, posicaoLuz);
    glDisable(GL_TEXTURE_2D);
    glEnable(GL_COLOR_MATERIAL);
    glEnable(GL_LIGHTING);
    glEnable(GL_LIGHT0);
    glEnable(GL_NORMALIZE);
    glEnable(GL_DEPTH_TEST);
    glMaterialfv(GL_FRONT,GL_SPECULAR, especularidade);
    glMateriali(GL_FRONT,GL_SHININESS,especMaterial);
    glLightfv(GL_LIGHT0, GL_DIFFUSE, luzDifusa );
}

// Função callback chamada quando o tamanho da janela é alterado
void alteraTamanhoJanela(GLsizei w, GLsizei h) {
    // Para previnir uma divisão por zero
    if ( h == 0 ) h = 1;

    // Especifica o tamanho da viewport
    glViewport(0, 0, w, h);

    largura = w;
    altura = h;

    frustum.setPerspective(40, (float) w / (float)h, 0.1, 3000.0);
}

void teclado(unsigned char tecla, int x, int y){
    switch(tecla){
        case 'w':
        case 'W': velocidadeFrente = sqrt(frustum.x*frustum.x + frustum.y*frustum.y + frustum.z*frustum.z);
                  break;

        case 's':
        case 'S': velocidadeFrente = -sqrt(frustum.x*frustum.x + frustum.y*frustum.y + frustum.z*frustum.z); break;

        case 'a':
        case 'A': velocidadeLado = -sqrt(frustum.x*frustum.x + frustum.y*frustum.y + frustum.z*frustum.z);
                  break;

        case 'd':
        case 'D': velocidadeLado = sqrt(frustum.x*frustum.x + frustum.y*frustum.y + frustum.z*frustum.z);
                  break;

        case ' ': velocidadeFrente = velocidadeLado = 0;
                  break;

        default:
            break;
    }
}

void tecladoEspecial(int tecla, int x, int y){
  switch(tecla){
        case GLUT_KEY_LEFT:
            break;

        case GLUT_KEY_RIGHT:
            break;

        case GLUT_KEY_DOWN:
            break;

        case GLUT_KEY_UP:
            break;

        default:
            break;
  }
}

// Funcao de simulacao de tempo
void simulacao(void){
    static clock_t tempoini = 0;
    clock_t  tempo;

    tempo = clock();
    dt = ((float) (tempo-tempoini) / (float) CLOCKS_PER_SEC);
    if(dt < EPSILON) return; // Nothing to do
    if(dt > 0.1) dt = 0.1;

    ////////////////////////////////////////////////////
    // Aqui podemos atualizar TODOS os agentes da cena    

    // Atualiza a posicao da camera em movimento. Mas só se for necessario.
    //if(fabs(velocidadeFrente) > 0.1 || fabs(velocidadeLado) > 0.1) {
    float x = frustum.x, y = frustum.x, z = frustum.z;  // Save

    frustum.advancePosition(velocidadeFrente * dt, 0.0, velocidadeLado * dt);
    if(sqrt(frustum.x*frustum.x + frustum.y*frustum.y + frustum.z*frustum.z) < 0.5) frustum.setPosition(x, y, z); // restore if too close
    frustum.setTarget(0, 0, 0);  // Ajusta as direcoes para visualizar o alvo.

    velocidadeFrente -= velocidadeFrente * dt * 2;
    velocidadeLado   -= velocidadeLado * dt * 2;

    if(glutGetWindow() != janela) glutSetWindow(janela);    
    glutPostRedisplay();

  tempoini = tempo;
}


// Função callback chamada para gerenciar eventos do mouse
void gerenciaMouse(int button, int state, int x, int y) {
    gira = FALSE;
    translada = FALSE;

    if(state == GLUT_DOWN && button == GLUT_LEFT_BUTTON){
        iniX = x;
        iniY = y;
        gira = TRUE;
    }

    if(state == GLUT_DOWN && button == GLUT_RIGHT_BUTTON){
        iniX = x;
        iniY = y;
        translada = TRUE;
    }
}

// Função callback chamada para gerenciar eventos do mouse
void gerenciaMovimentoMouse(int x, int y) {
    if(gira) {
        frustum.rotateOrbitTrackball(0, 0, 0, (largura - 2.f * iniX) / largura, (altura - 2.f * y) / altura, (largura-2.f * x) / largura, (altura - 2.f * iniY) / altura);
        frustum.setTarget(0, 0, 0);  // Ajusta as direcoes para visualizar o alvo.
        //frustum.alignAxis();
        glutPostRedisplay();
    }

    if(translada) {
        frustum.advancePosition(.3*(x - iniX), 0.0, 0.0);
        glutPostRedisplay();
    }
    iniX = x;
    iniY = y;
}



void desenhaTudo() {
    int     i = 0;
    float   angulo;
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    frustum.exportOpenGL();    
    

    glColor3f(1.0, 1.0, 1.0);
    
    clock_t tempo = clock();
    float qps;    
    tempoant = tempo;

    //Executa os comandos OpenGL e troca os buffers
    glutSwapBuffers();
}


void systemexit() {
    glutDestroyWindow( janela );
}

int main(int argv, char** argc) {
    atexit(systemexit);

    glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB);
    glutInitWindowSize(800,600);
    glutInitWindowPosition(200,120);

    janela = glutCreateWindow("GCG - DCC/DF/UFJF - Molecular Dynamics Simulation");
    frustum.setPosition( 88.301239, 74.250556, 114.573692 );
    frustum.setViewVector( 0.543026, 0.456803, 0.704594 );
    frustum.setUpVector( -0.270065, 0.889513, -0.368553 );

    ConfiguraOpenGL();                       //inicializa funções do openGL    

    glutReshapeFunc(alteraTamanhoJanela);    //chamada cada vez que um tamanho de janela é modificado
    glutDisplayFunc(desenhaTudo);            //chamada sempre que a janela precisar ser redesenhada
    glutKeyboardFunc(teclado);               //chamada cada vez que uma tecla de código ASCII é pressionado
    glutSpecialFunc(tecladoEspecial);        //chamada cada vez que uma tecla de código não ASCII é pressionado
    glutMotionFunc(gerenciaMovimentoMouse);
    glutMouseFunc(gerenciaMouse);
    GLUI_Master.set_glutIdleFunc(simulacao);    


    glutMainLoop();  //mantêm GLUT em loop, considerando eventos acima
    return 0; // Return para evitar warnigns
}
